あるGameObjectのTagをスクリプトで判定する時、文字列直打ちでは柔軟性に欠けます 手動で定数化した場合も更新の保守しなければ行けない為、ここはUnityEditor拡張機能に頼り自動生成してもらうようにします
Tagとは
GameObject名の下にあるTag一覧からタグの変更ができます
スクリプトでは tag もしくは ComparateTag から判定を行うことができます
private bool IsItem(GameObject obj)
{
return obj.CompareTag("Item");
}
タグ一覧から任意のタグを追加できます
スクリプトからは
var tags = InternalEditorUtility.tags;
で取得することが可能です。 これを使用し追加されたタグをスクリプトに出力します
Script Template
雛形からスクリプトを生成するようにします
- namespace #NAMESPACE#
- クラス名 #SCRIPTNAME#
- 定義 #PARAM#
namespace #NAMESPACE#
{
public partial class #SCRIPTNAME#
{
#PARAM#
}
}
で囲まれた文字列をEditor拡張機能から置換してスクリプトを作成します
上記を ConstClass.txt
のような名前で適当なフォルダに突っ込んでおきます
ScriptCreator
実際の拡張機能クラスを作成します まずは、
- テンプレートを読み込む
- #で囲まれた文字列を置換する
- 出力する
を行う ScriptCreator
の作成を行います
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
public class ScriptCreator
{
public const string TemplateDirectory = "Assets/Editor/ScriptTemplates/";
public class Param
{
public string ScriptName;
public string TemplateName;
public string DirectoryPath;
public Dictionary<string, string> ReplaceDict = new Dictionary<string, string>();
}
public static void Execute(Param param)
{
// テンプレート
var templatePath = $"{TemplateDirectory}/{param.TemplateName}.txt";
string scriptText;
using (var streamReader = new StreamReader(templatePath))
{
scriptText = streamReader.ReadToEnd();
}
// 各項目を置換
foreach (var pair in param.ReplaceDict)
{
scriptText = scriptText.Replace(pair.Key, pair.Value);
}
// 出力
var exportPath = $"{param.DirectoryPath}/{param.ScriptName}.cs";
File.WriteAllText(exportPath, scriptText, System.Text.Encoding.UTF8);
AssetDatabase.Refresh(ImportAssetOptions.ImportRecursive);
}
}
行っていることは単純で、 ReplaceDict
Dictionaryで渡された文字を置換します。
次にTagを読み込み、必要なパラメータをScriptCreatorにわたす TagDefinieCreator
クラスを作成します
using System.Collections.Generic;
using UnityEditor;
using System;
using System.Text;
using System.Linq;
using UnityEditorInternal;
public class TagDefineCreator
{
public const string SaveDirectory = "Assets/Scripts/";
public const string ScriptName = "TagDefine";
public const string TemplateName = "DefineClass";
[MenuItem("Tools/ScriptCreator/TagDefineCreate")]
public static void Execute()
{
var valueDict = InternalEditorUtility.tags.ToDictionary(x => x, x => x);
// 定数作成メソッドにあとは任せる
Create(valueDict, ScriptName, "Test");
}
public static void Create<T>(Dictionary<string, T> valueDict, string scriptName, string namespceName)
{
var sb = new StringBuilder();
// 最大の文字数
var keyLengthMax = valueDict.Keys.Max(x => x.Length);
var typeCode = Type.GetTypeCode(typeof(T));
bool isFirst = true;
foreach (var pair in valueDict)
{
var value = pair.Value.ToString();
(string TypeStr, string ValueStr) str = typeCode switch
{
TypeCode.Int32 => ("int", value),
TypeCode.Int64 => ("long", value),
TypeCode.Double => ("double", value),
TypeCode.String => ("string", $"\"{value}\""), // ""で囲む
_ => (null, null)
};
if (!isFirst)
{
sb.AppendLine();
}
isFirst = false;
sb.Append($"\t\tpublic const {str.TypeStr} {pair.Key} = {str.ValueStr};");
}
var createParam = new ScriptCreator.Param();
createParam.ReplaceDict["#PARAM#"] = sb.ToString();
createParam.ReplaceDict["#SCRIPTNAME#"] = scriptName;
createParam.ReplaceDict["#NAMESPACE#"] = namespceName;
createParam.DirectoryPath = SaveDirectory;
createParam.ScriptName = scriptName;
createParam.TemplateName = TemplateName;
ScriptCreator.Execute(createParam);
}
}
Tools/ScriptCreator/TagDefineCreate
で、メニューから実行された場合、TagをDictionaryに変換し、必要なパラメータと共にScriptCreatorに渡しています
今回はstring型ですが、 int などを定数化したい場合を考え
var typeCode = Type.GetTypeCode(typeof(T));
で型を取得することにしました。
後はScriptCreator.ParamのDictionaryに置き換える文字列を格納し実行しています
結果、以下のScriptが作成されました
namespace Test
{
public partial class TagDefine
{
public const string Untagged = "Untagged";
public const string Respawn = "Respawn";
public const string Finish = "Finish";
public const string EditorOnly = "EditorOnly";
public const string MainCamera = "MainCamera";
public const string Player = "Player";
public const string GameController = "GameController";
}
}
終わり
今回の機能はかなり簡素な作りにしています。 他にも
- Layers
- Sorint Layers
も定数化したいケースが出てくると思います
その時は TagDefineCreator
の中身から定数化に必要な部分を抜き出し DefineCreator というクラスを一枚噛ませるようにすれば処理の共有化が出来ます